package com.fight.strive.sys.modules.rbac.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fight.strive.sys.modules.rbac.constants.RbacAuthConstants;
import com.fight.strive.sys.modules.rbac.dao.RbacAuthResourceMapper;
import com.fight.strive.sys.modules.rbac.dto.RbacAuthResourceDto;
import com.fight.strive.sys.modules.rbac.entity.RbacAuthEntity;
import com.fight.strive.sys.modules.rbac.entity.RbacAuthResourceEntity;
import com.fight.strive.sys.modules.rbac.entity.RbacElemEntity;
import com.fight.strive.sys.modules.rbac.entity.RbacMenuEntity;
import com.fight.strive.sys.modules.rbac.entity.RbacResEntity;
import com.fight.strive.sys.modules.rbac.service.RbacAuthResourceService;
import com.fight.strive.sys.modules.rbac.service.RbacAuthService;
import com.fight.strive.sys.modules.rbac.service.RbacElemService;
import com.fight.strive.sys.modules.rbac.service.RbacMenuService;
import com.fight.strive.sys.modules.rbac.service.RbacResService;
import com.fight.strive.sys.utils.CollectionUtils;
import com.fight.strive.sys.utils.ObjectUtils;
import com.fight.strive.sys.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;

/**
 * @author ZHOUXIANG
 */
@Service
@Slf4j
public class RbacAuthResourceServiceImpl
        extends ServiceImpl<RbacAuthResourceMapper, RbacAuthResourceEntity>
        implements RbacAuthResourceService {

    @Resource
    private RbacAuthService rbacAuthService;

    @Resource
    private RbacMenuService rbacMenuService;

    @Resource
    private RbacElemService rbacElemService;

    @Resource
    private RbacResService rbacResService;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void save(@Valid RbacAuthResourceDto dto) {
        // 删除已有关联
        this.delete(dto);
        // 设置关联关系
        RbacAuthEntity authEntity = rbacAuthService.getById(dto.getAuthId());
        if (ObjectUtils.notNull(authEntity)) {
            if (CollectionUtils.isNotEmpty(dto.getResourceIds())) {
                switch (authEntity.getAuthTypeCode()) {
                    case "MENU":
                        this.handleMenu(dto, authEntity);
                        break;
                    case "ELEMENT":
                        this.handleElem(dto, authEntity);
                        break;
                    case "RESOURCE":
                        this.handleRes(dto, authEntity);
                        break;
                    default:
                        break;
                }
            }
        }
    }

    @Override
    public void delete(@Valid RbacAuthResourceDto dto) {
        // 删除缓存
        this.deleteRedisData(dto.getAuthId());
        // 删除数据库
        QueryWrapper<RbacAuthResourceEntity> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("auth_id", dto.getAuthId());
        this.remove(queryWrapper);
    }

    @Override
    @SuppressWarnings("unchecked")
    public List<Long> getIds(Long authId) {
        List<Long> ids = (List<Long>) redisTemplate.opsForHash().get(
                RbacAuthConstants.AUTH_RES_IDS_KEY, authId);
        if (ObjectUtils.isNull(ids)) {
            ids = new ArrayList<>();
            QueryWrapper<RbacAuthResourceEntity> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("auth_id", authId);
            List<RbacAuthResourceEntity> list = this.list(queryWrapper);
            if (CollectionUtils.isNotEmpty(list)) {
                for (RbacAuthResourceEntity entity:list) {
                    ids.add(entity.getResId());
                }
            }
            // 缓存到 redis 空列表也缓存 []。
            redisTemplate.opsForHash().put(
                    RbacAuthConstants.AUTH_RES_IDS_KEY, authId, ids);
        }
        return ids;
    }

    @Override
    public HashSet<String> getUserAuthResource(HashSet<String> auths, String authType) {
        HashSet<String> hashSet = new HashSet<>();
        if (CollectionUtils.isNotEmpty(auths)) {
            // this.getAuthResource 走缓存，有就从缓中拿，没有就查数据库
            auths.forEach(au -> hashSet.addAll(
                    this.getAuthResource(au, authType)));
        }
        return hashSet;
    }

    @Override
    public HashSet<String> getAuthResource(String authCode, String authType) {
        HashSet<String> hashSet = new HashSet<>();
        RbacAuthEntity authEntity = rbacAuthService.getAuthByCode(authCode);
        if (ObjectUtils.notNull(authEntity) && StringUtils.equalsIgnoreCase(
                authType, authEntity.getAuthTypeCode())) {
            // 必须符合权限类型
            List<RbacAuthResourceEntity> list =
                    this.getAuthResByAuthId(authEntity.getId());
            if (CollectionUtils.isNotEmpty(list)) {
                list.forEach(ar -> hashSet.add(ar.getCode()));
            }
        }
        return hashSet;
    }

    @Override
    public HashSet<String> getUserRes(Long userId, String type) {
        HashSet<String> resCodes = new HashSet<>();
        // 缓存有就走缓存，没有就走DB
        HashSet<String> userAuths = rbacAuthService.getUserAuths(userId);
        if (CollectionUtils.isNotEmpty(userAuths)) {
            resCodes = this.getUserAuthResource(userAuths, type);
        }
        return resCodes;
    }

    @Override
    public boolean userHasRes(Long userId, String type, String resCode) {
        HashSet<String> resCodes = this.getUserRes(userId, type);
        return CollectionUtils.isNotEmpty(resCodes)
                && resCodes.contains(resCode);
    }

    @Override
    @SuppressWarnings("unchecked")
    public List<RbacAuthResourceEntity> getAuthResByAuthId(Long authId) {
        List<RbacAuthResourceEntity> authResList =
                (List<RbacAuthResourceEntity>) redisTemplate.opsForHash().get(
                        RbacAuthConstants.AUTH_RES_LIST_KEY, authId);
        if (CollectionUtils.isNotEmpty(authResList)) {
            QueryWrapper<RbacAuthResourceEntity> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("auth_id", authId);
            authResList = this.list(queryWrapper);
            // 空列表也缓存
            this.updateRedisData(authId, authResList);
        }
        return authResList;
    }

    @Override
    public List<RbacAuthResourceEntity> getAuthResByAuthCode(String authCode) {
        RbacAuthEntity authEntity = rbacAuthService.getAuthByCode(authCode);
        if (ObjectUtils.notNull(authEntity)) {
            return this.getAuthResByAuthId(authEntity.getId());
        }
        return Collections.emptyList();
    }

    @Override
    public void updateRedisData(Long authId,
                                List<RbacAuthResourceEntity> authResList) {
        // auth id -> auth resource entity list
        redisTemplate.opsForHash().put(
                RbacAuthConstants.AUTH_RES_LIST_KEY, authId, authResList);
        List<Long> ids = new ArrayList<>();
        HashSet<String> resCodes = new HashSet<>();
        for (RbacAuthResourceEntity entity : authResList) {
            ids.add(entity.getResId());
            resCodes.add(entity.getCode());
        }
        redisTemplate.opsForHash().put(
                RbacAuthConstants.AUTH_RES_IDS_KEY, authId, ids);
        redisTemplate.opsForHash().put(
                RbacAuthConstants.AUTH_RES_CODES_KEY, authId, resCodes);
    }

    @Override
    public void deleteRedisData(Long authId) {
        redisTemplate.opsForHash().delete(
                RbacAuthConstants.AUTH_RES_LIST_KEY, authId);
        redisTemplate.opsForHash().delete(
                RbacAuthConstants.AUTH_RES_IDS_KEY, authId);
        redisTemplate.opsForHash().delete(
                RbacAuthConstants.AUTH_RES_CODES_KEY, authId);
    }

    /**
     * 保存权限菜单关系
     *
     * @param dto        权限菜单关系
     * @param authEntity 权限信息
     */
    private void handleMenu(RbacAuthResourceDto dto, RbacAuthEntity authEntity) {
        List<RbacAuthResourceEntity> list = new ArrayList<>();
        dto.getResourceIds().forEach(id -> {
            RbacMenuEntity menuEntity = rbacMenuService.getMenuById(id);
            if (ObjectUtils.notNull(menuEntity)) {
                RbacAuthResourceEntity rbacAuthResourceEntity
                        = new RbacAuthResourceEntity();
                rbacAuthResourceEntity.setAuthId(authEntity.getId())
                        .setAuthCode(authEntity.getAuthCode())
                        .setAuthName(authEntity.getAuthName())
                        .setAuthTypeCode(authEntity.getAuthTypeCode())
                        .setAuthTypeName(authEntity.getAuthTypeName())
                        .setResId(menuEntity.getId())
                        .setCode(menuEntity.getMenuCode())
                        .setName(menuEntity.getMenuName());
                this.saveOrUpdate(rbacAuthResourceEntity);
                list.add(rbacAuthResourceEntity);
            }
        });
        this.updateRedisData(authEntity.getId(), list);
    }

    /**
     * 保存权限元素关系
     *
     * @param dto        权限元素关系
     * @param authEntity 权限信息
     */
    private void handleElem(RbacAuthResourceDto dto, RbacAuthEntity authEntity) {
        List<RbacAuthResourceEntity> list = new ArrayList<>();
        dto.getResourceIds().forEach(id -> {
            RbacElemEntity elemEntity = rbacElemService.getElemById(id);
            if (ObjectUtils.notNull(elemEntity)) {
                RbacAuthResourceEntity rbacAuthResourceEntity
                        = new RbacAuthResourceEntity();
                rbacAuthResourceEntity.setAuthId(authEntity.getId())
                        .setAuthCode(authEntity.getAuthCode())
                        .setAuthName(authEntity.getAuthName())
                        .setAuthTypeCode(authEntity.getAuthTypeCode())
                        .setAuthTypeName(authEntity.getAuthTypeName())
                        .setResId(elemEntity.getId())
                        .setCode(elemEntity.getElemCode())
                        .setName(elemEntity.getElemName());
                this.saveOrUpdate(rbacAuthResourceEntity);
                list.add(rbacAuthResourceEntity);
            }
        });
        this.updateRedisData(authEntity.getId(), list);
    }

    /**
     * 保存权限资源关系
     *
     * @param dto        权限资源关系
     * @param authEntity 权限信息
     */
    private void handleRes(RbacAuthResourceDto dto, RbacAuthEntity authEntity) {
        List<RbacAuthResourceEntity> list = new ArrayList<>();
        dto.getResourceIds().forEach(id -> {
            RbacResEntity resEntity = rbacResService.getResById(id);
            if (ObjectUtils.notNull(resEntity)) {
                RbacAuthResourceEntity rbacAuthResourceEntity
                        = new RbacAuthResourceEntity();
                rbacAuthResourceEntity.setAuthId(authEntity.getId())
                        .setAuthCode(authEntity.getAuthCode())
                        .setAuthName(authEntity.getAuthName())
                        .setAuthTypeCode(authEntity.getAuthTypeCode())
                        .setAuthTypeName(authEntity.getAuthTypeName())
                        .setResId(resEntity.getId())
                        .setCode(resEntity.getResUri())
                        .setName(resEntity.getResName());
                this.saveOrUpdate(rbacAuthResourceEntity);
                list.add(rbacAuthResourceEntity);
            }
        });
        this.updateRedisData(authEntity.getId(), list);
    }
}
